﻿namespace NTLib.Core.Configuration
{
    using Exceptions;
    using Exceptions.Configuration;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Threading.Tasks;
    using Threading;

    /// <summary>
    /// Define a base thread safe implementation of the interface <see cref="IConfigurable" />
    /// </summary>
    /// <seealso cref="NTLib.Core.Configuration.IConfigurable" />
    [DataContract]
    public abstract class BaseConfigurable : IConfigurable
    {
        #region Fields

        private readonly ReaderWriterLocker _locker;
        private readonly bool _allowMultipleConfig;
        private bool _isConfigured;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseConfigurable"/> class.
        /// </summary>
        /// <param name="allowMultipleConfiguration">Define that the instance could receive multiple called of the config method without the reset called</param>
        protected BaseConfigurable(bool allowMultipleConfiguration = false)
        {
            this._allowMultipleConfig = allowMultipleConfiguration;
            this._locker = new ReaderWriterLocker();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets a value indicating whether this instance is configured.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is configured; otherwise, <c>false</c>.
        /// </value>
        /// <remarks>
        /// The value must changed after the call of LoadConfig and ResetConfig methods.
        /// </remarks>
        [IgnoreDataMember]
        public bool IsConfigured
        {
            get
            {
                using (this._locker.ScopeLock(true))
                {
                    return this._isConfigured;
                }
            }
            private set { this._isConfigured = value; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Loads the configuration.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="configs">The configs.</param>
        /// <exception cref="NTLib.Core.Exceptions.Configuration.AlreadyConfiguredException"></exception>
        /// <exception cref="NTLib.Core.Exceptions.Configuration.ConfigurationFailedException"></exception>
        /// <exception cref="NTLib.Core.Exceptions.Configuration.ConfigurationException"></exception>
        /// <exception cref="BadExceptionUsedException{ConfigurationException}">This exception is raised if the reset config doesn't use the ConfigurationException class.</exception>
        public void LoadConfig(ConfigurationCollection configs)
        {
            try
            {
                if (this.IsConfigured && !this._allowMultipleConfig)
                {
                    throw new AlreadyConfiguredException();
                }
                using (this._locker.ScopeLock())
                {
                    if (!this._isConfigured || this._allowMultipleConfig)
                    {
                        Debug.WriteLine("PreOnLoadConfig");
                        OnLoadConfig(configs);
                        Debug.WriteLine("PostOnLoadConfig");

                        this._isConfigured = true;
                    }
                }

            }
            catch (ConfigurationFailedException)
            {
                throw;
            }
            catch (ConfigurationException ex)
            {
                throw new ConfigurationFailedException(String.Format(Strings.ExceptionResources.ConfigurationFailedException, ex));
            }
            catch (Exception ex)
            {
                throw new BadExceptionUsedException<ConfigurationException>(ex);
            }
        }

        /// <summary>
        /// Resets the configuration.
        /// </summary>
        /// <exception cref="NTLib.Core.Exceptions.Configuration.ConfigurationResetFailedException"></exception>
        /// <exception cref="NTLib.Core.Exceptions.Configuration.ConfigurationException"></exception>
        /// <exception cref="BadExceptionUsedException{ConfigurationException}">This exception is raised if the reset config doesn't use the ConfigurationException class.</exception>
        public void ResetConfig()
        {
            try
            {
                using (this._locker.ScopeLock())
                {
                    if (!this._isConfigured)
                    {
                        OnResetConfig();

                        this._isConfigured = false;
                    }
                }
            }
            catch (ConfigurationResetFailedException)
            {
                throw;
            }
            catch (ConfigurationException ex)
            {
                throw new ConfigurationResetFailedException(String.Format(Strings.ExceptionResources.ConfigurationResetFailedException, ex));
            }
            catch (Exception ex)
            {
                throw new BadExceptionUsedException<ConfigurationException>(ex);
            }
        }

        /// <summary>
        /// Called to Loads the configuration.
        /// </summary>
        /// <param name="configs">The configuration values.</param>
        /// <exception cref="ConfigurationException">You MUST only raise exception that inherite from the ConfigurationException base class</exception>
        /// <exception cref="ConfigurationFailedException">Raise this type of expression to describe why the load configuration failed.</exception>
        protected abstract void OnLoadConfig(ConfigurationCollection configs);

        /// <summary>
        /// Called to reset the configuration.
        /// </summary>
        /// <exception cref="ConfigurationException">You MUST only raise exception that inherite from the ConfigurationException base class</exception>
        /// <exception cref="ConfigurationResetFailedException">Raise this type of expression to describe why the reset failed.</exception>
        protected abstract void OnResetConfig();

        #endregion
    }
}
